/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package org.dom4j.io;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.dom4j.Attribute;
import org.dom4j.Branch;
import org.dom4j.CDATA;
import org.dom4j.CharacterData;
import org.dom4j.Comment;
import org.dom4j.Document;
import org.dom4j.DocumentType;
import org.dom4j.Element;
import org.dom4j.Entity;
import org.dom4j.Namespace;
import org.dom4j.Node;
import org.dom4j.ProcessingInstruction;
import org.dom4j.Text;
import org.dom4j.tree.NamespaceStack;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.LocatorImpl;

/**
 * <p> <code>SAXWriter</code> writes a DOM4J tree to a SAX ContentHandler.
 * </p>
 * 
 * @author <a href="mailto:james.strachan@metastuff.com">James Strachan </a>
 * @version $Revision: 1.2 $
 */
public class SAXWriter implements XMLReader
{
   protected static final String[] LEXICAL_HANDLER_NAMES =
      { "http://xml.org/sax/properties/lexical-handler",
         "http://xml.org/sax/handlers/LexicalHandler" };

   protected static final String FEATURE_NAMESPACE_PREFIXES =
      "http://xml.org/sax/features/namespace-prefixes";

   protected static final String FEATURE_NAMESPACES =
      "http://xml.org/sax/features/namespaces";

   /** <code>ContentHandler</code> to which SAX events are raised */
   private ContentHandler contentHandler;

   /** <code>DTDHandler</code> fired when a document has a DTD */
   private DTDHandler dtdHandler;

   /** <code>EntityResolver</code> fired when a document has a DTD */
   private EntityResolver entityResolver;

   private ErrorHandler errorHandler;

   /** <code>LexicalHandler</code> fired on Entity and CDATA sections */
   private LexicalHandler lexicalHandler;

   /** <code>AttributesImpl</code> used when generating the Attributes */
   private AttributesImpl attributes = new AttributesImpl();

   /** Stores the features */
   private Map features = new HashMap();

   /** Stores the properties */
   private Map properties = new HashMap();

   /** Whether namespace declarations are exported as attributes or not */
   private boolean declareNamespaceAttributes;

   public SAXWriter()
   {
      properties.put(FEATURE_NAMESPACE_PREFIXES, Boolean.FALSE);
      properties.put(FEATURE_NAMESPACE_PREFIXES, Boolean.TRUE);
   }

   public SAXWriter(ContentHandler contentHandler)
   {
      this();
      this.contentHandler = contentHandler;
   }

   public SAXWriter(ContentHandler contentHandler, LexicalHandler lexicalHandler)
   {
      this();
      this.contentHandler = contentHandler;
      this.lexicalHandler = lexicalHandler;
   }

   public SAXWriter(ContentHandler contentHandler,
      LexicalHandler lexicalHandler, EntityResolver entityResolver)
   {
      this();
      this.contentHandler = contentHandler;
      this.lexicalHandler = lexicalHandler;
      this.entityResolver = entityResolver;
   }

   /**
    * A polymorphic method to write any Node to this SAX stream
    * 
    * @param node DOCUMENT ME!
    * 
    * @throws SAXException DOCUMENT ME!
    */
   public void write(Node node) throws SAXException
   {
      int nodeType = node.getNodeType();

      switch(nodeType)
      {
      case Node.ELEMENT_NODE:
         write((Element) node);

         break;

      case Node.ATTRIBUTE_NODE:
         write((Attribute) node);

         break;

      case Node.TEXT_NODE:
         write(node.getText());

         break;

      case Node.CDATA_SECTION_NODE:
         write((CDATA) node);

         break;

      case Node.ENTITY_REFERENCE_NODE:
         write((Entity) node);

         break;

      case Node.PROCESSING_INSTRUCTION_NODE:
         write((ProcessingInstruction) node);

         break;

      case Node.COMMENT_NODE:
         write((Comment) node);

         break;

      case Node.DOCUMENT_NODE:
         write((Document) node);

         break;

      case Node.DOCUMENT_TYPE_NODE:
         write((DocumentType) node);

         break;

      case Node.NAMESPACE_NODE:

         // Will be output with attributes
         // write((Namespace) node);
         break;

      default:
         throw new SAXException("Invalid node type: " + node);
      }
   }

   /**
    * Generates SAX events for the given Document and all its content
    * 
    * @param document is the Document to parse
    * 
    * @throws SAXException if there is a SAX error processing the events
    */
   public void write(Document document) throws SAXException
   {
      if(document != null)
      {
         checkForNullHandlers();

         documentLocator(document);
         startDocument();
         entityResolver(document);
         dtdHandler(document);

         writeContent(document, new NamespaceStack());
         endDocument();
      }
   }

   /**
    * Generates SAX events for the given Element and all its content
    * 
    * @param element is the Element to parse
    * 
    * @throws SAXException if there is a SAX error processing the events
    */
   public void write(Element element) throws SAXException
   {
      write(element, new NamespaceStack());
   }

   /**
    * <p> Writes the opening tag of an {@link Element}, including its {@link
    * Attribute}s but without its content. </p>
    * 
    * @param element <code>Element</code> to output.
    * 
    * @throws SAXException DOCUMENT ME!
    */
   public void writeOpen(Element element) throws SAXException
   {
      startElement(element, null);
   }

   /**
    * <p> Writes the closing tag of an {@link Element} </p>
    * 
    * @param element <code>Element</code> to output.
    * 
    * @throws SAXException DOCUMENT ME!
    */
   public void writeClose(Element element) throws SAXException
   {
      endElement(element);
   }

   /**
    * Generates SAX events for the given text
    * 
    * @param text is the text to send to the SAX ContentHandler
    * 
    * @throws SAXException if there is a SAX error processing the events
    */
   public void write(String text) throws SAXException
   {
      if(text != null)
      {
         char[] chars = text.toCharArray();
         contentHandler.characters(chars, 0, chars.length);
      }
   }

   /**
    * Generates SAX events for the given CDATA
    * 
    * @param cdata is the CDATA to parse
    * 
    * @throws SAXException if there is a SAX error processing the events
    */
   public void write(CDATA cdata) throws SAXException
   {
      String text = cdata.getText();

      if(lexicalHandler != null)
      {
         lexicalHandler.startCDATA();
         write(text);
         lexicalHandler.endCDATA();
      }
      else
      {
         write(text);
      }
   }

   /**
    * Generates SAX events for the given Comment
    * 
    * @param comment is the Comment to parse
    * 
    * @throws SAXException if there is a SAX error processing the events
    */
   public void write(Comment comment) throws SAXException
   {
      if(lexicalHandler != null)
      {
         String text = comment.getText();
         char[] chars = text.toCharArray();
         lexicalHandler.comment(chars, 0, chars.length);
      }
   }

   /**
    * Generates SAX events for the given Entity
    * 
    * @param entity is the Entity to parse
    * 
    * @throws SAXException if there is a SAX error processing the events
    */
   public void write(Entity entity) throws SAXException
   {
      String text = entity.getText();

      if(lexicalHandler != null)
      {
         String name = entity.getName();
         lexicalHandler.startEntity(name);
         write(text);
         lexicalHandler.endEntity(name);
      }
      else
      {
         write(text);
      }
   }

   /**
    * Generates SAX events for the given ProcessingInstruction
    * 
    * @param pi is the ProcessingInstruction to parse
    * 
    * @throws SAXException if there is a SAX error processing the events
    */
   public void write(ProcessingInstruction pi) throws SAXException
   {
      String target = pi.getTarget();
      String text = pi.getText();
      contentHandler.processingInstruction(target, text);
   }

   /**
    * Should namespace declarations be converted to "xmlns" attributes. This
    * property defaults to <code>false</code> as per the SAX specification.
    * This property is set via the SAX feature
    * "http://xml.org/sax/features/namespace-prefixes"
    * 
    * @return DOCUMENT ME!
    */
   public boolean isDeclareNamespaceAttributes()
   {
      return declareNamespaceAttributes;
   }

   /**
    * Sets whether namespace declarations should be exported as "xmlns"
    * attributes or not. This property is set from the SAX feature
    * "http://xml.org/sax/features/namespace-prefixes"
    * 
    * @param declareNamespaceAttrs DOCUMENT ME!
    */
   public void setDeclareNamespaceAttributes(boolean declareNamespaceAttrs)
   {
      this.declareNamespaceAttributes = declareNamespaceAttrs;
   }

   // XMLReader methods
   // -------------------------------------------------------------------------

   /**
    * DOCUMENT ME!
    * 
    * @return the <code>ContentHandler</code> called when SAX events are
    *         raised
    */
   public ContentHandler getContentHandler()
   {
      return contentHandler;
   }

   /**
    * Sets the <code>ContentHandler</code> called when SAX events are raised
    * 
    * @param contentHandler is the <code>ContentHandler</code> called when SAX
    *           events are raised
    */
   public void setContentHandler(ContentHandler contentHandler)
   {
      this.contentHandler = contentHandler;
   }

   /**
    * DOCUMENT ME!
    * 
    * @return the <code>DTDHandler</code>
    */
   public DTDHandler getDTDHandler()
   {
      return dtdHandler;
   }

   /**
    * Sets the <code>DTDHandler</code>.
    * 
    * @param handler DOCUMENT ME!
    */
   public void setDTDHandler(DTDHandler handler)
   {
      this.dtdHandler = handler;
   }

   /**
    * DOCUMENT ME!
    * 
    * @return the <code>ErrorHandler</code>
    */
   public ErrorHandler getErrorHandler()
   {
      return errorHandler;
   }

   /**
    * Sets the <code>ErrorHandler</code>.
    * 
    * @param errorHandler DOCUMENT ME!
    */
   public void setErrorHandler(ErrorHandler errorHandler)
   {
      this.errorHandler = errorHandler;
   }

   /**
    * DOCUMENT ME!
    * 
    * @return the <code>EntityResolver</code> used when a Document contains a
    *         DTD
    */
   public EntityResolver getEntityResolver()
   {
      return entityResolver;
   }

   /**
    * Sets the <code>EntityResolver</code>.
    * 
    * @param entityResolver is the <code>EntityResolver</code>
    */
   public void setEntityResolver(EntityResolver entityResolver)
   {
      this.entityResolver = entityResolver;
   }

   /**
    * DOCUMENT ME!
    * 
    * @return the <code>LexicalHandler</code> used when a Document contains a
    *         DTD
    */
   public LexicalHandler getLexicalHandler()
   {
      return lexicalHandler;
   }

   /**
    * Sets the <code>LexicalHandler</code>.
    * 
    * @param lexicalHandler is the <code>LexicalHandler</code>
    */
   public void setLexicalHandler(LexicalHandler lexicalHandler)
   {
      this.lexicalHandler = lexicalHandler;
   }

   /**
    * Sets the <code>XMLReader</code> used to write SAX events to
    * 
    * @param xmlReader is the <code>XMLReader</code>
    */
   public void setXMLReader(XMLReader xmlReader)
   {
      setContentHandler(xmlReader.getContentHandler());
      setDTDHandler(xmlReader.getDTDHandler());
      setEntityResolver(xmlReader.getEntityResolver());
      setErrorHandler(xmlReader.getErrorHandler());
   }

   /**
    * Looks up the value of a feature.
    * 
    * @param name DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
    * 
    * @throws SAXNotRecognizedException DOCUMENT ME!
    * @throws SAXNotSupportedException DOCUMENT ME!
    */
   public boolean getFeature(String name) throws SAXNotRecognizedException,
      SAXNotSupportedException
   {
      Boolean answer = (Boolean) features.get(name);

      return (answer != null) && answer.booleanValue();
   }

   /**
    * This implementation does actually use any features but just stores them
    * for later retrieval
    * 
    * @param name DOCUMENT ME!
    * @param value DOCUMENT ME!
    * 
    * @throws SAXNotRecognizedException DOCUMENT ME!
    * @throws SAXNotSupportedException DOCUMENT ME!
    */
   public void setFeature(String name, boolean value)
      throws SAXNotRecognizedException, SAXNotSupportedException
   {
      if(FEATURE_NAMESPACE_PREFIXES.equals(name))
      {
         setDeclareNamespaceAttributes(value);
      }
      else if(FEATURE_NAMESPACE_PREFIXES.equals(name))
      {
         if(!value)
         {
            String msg = "Namespace feature is always supported in dom4j";
            throw new SAXNotSupportedException(msg);
         }
      }

      features.put(name, (value) ? Boolean.TRUE : Boolean.FALSE);
   }

   /**
    * Sets the given SAX property
    * 
    * @param name DOCUMENT ME!
    * @param value DOCUMENT ME!
    */
   public void setProperty(String name, Object value)
   {
      for(int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++)
      {
         if(LEXICAL_HANDLER_NAMES[i].equals(name))
         {
            setLexicalHandler((LexicalHandler) value);

            return;
         }
      }

      properties.put(name, value);
   }

   /**
    * Gets the given SAX property
    * 
    * @param name DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
    * 
    * @throws SAXNotRecognizedException DOCUMENT ME!
    * @throws SAXNotSupportedException DOCUMENT ME!
    */
   public Object getProperty(String name) throws SAXNotRecognizedException,
      SAXNotSupportedException
   {
      for(int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++)
      {
         if(LEXICAL_HANDLER_NAMES[i].equals(name))
         {
            return getLexicalHandler();
         }
      }

      return properties.get(name);
   }

   /**
    * This method is not supported.
    * 
    * @param systemId DOCUMENT ME!
    * 
    * @throws SAXNotSupportedException DOCUMENT ME!
    */
   public void parse(String systemId) throws SAXNotSupportedException
   {
      throw new SAXNotSupportedException("This XMLReader can only accept"
         + " <dom4j> InputSource objects");
   }

   /**
    * Parses an XML document. This method can only accept DocumentInputSource
    * inputs otherwise a {@link SAXNotSupportedException}exception is thrown.
    * 
    * @param input DOCUMENT ME!
    * 
    * @throws SAXException DOCUMENT ME!
    * @throws SAXNotSupportedException if the input source is not wrapping a
    *            dom4j document
    */
   public void parse(InputSource input) throws SAXException
   {
      if(input instanceof DocumentInputSource)
      {
         DocumentInputSource documentInput = (DocumentInputSource) input;
         Document document = documentInput.getDocument();
         write(document);
      }
      else
      {
         throw new SAXNotSupportedException("This XMLReader can only accept "
            + "<dom4j> InputSource objects");
      }
   }

   // Implementation methods
   // -------------------------------------------------------------------------
   protected void writeContent(Branch branch, NamespaceStack namespaceStack)
      throws SAXException
   {
      for(Iterator iter = branch.nodeIterator(); iter.hasNext();)
      {
         Object object = iter.next();

         if(object instanceof Element)
         {
            write((Element) object, namespaceStack);
         }
         else if(object instanceof CharacterData)
         {
            if(object instanceof Text)
            {
               Text text = (Text) object;
               write(text.getText());
            }
            else if(object instanceof CDATA)
            {
               write((CDATA) object);
            }
            else if(object instanceof Comment)
            {
               write((Comment) object);
            }
            else
            {
               throw new SAXException("Invalid Node in DOM4J content: "
                  + object + " of type: " + object.getClass());
            }
         }
         else if(object instanceof String)
         {
            write((String) object);
         }
         else if(object instanceof Entity)
         {
            write((Entity) object);
         }
         else if(object instanceof ProcessingInstruction)
         {
            write((ProcessingInstruction) object);
         }
         else if(object instanceof Namespace)
         {
            write((Namespace) object);
         }
         else
         {
            throw new SAXException("Invalid Node in DOM4J content: " + object);
         }
      }
   }

   /**
    * The {@link org.xml.sax.Locator}is only really useful when parsing a
    * textual document as its main purpose is to identify the line and column
    * number. Since we are processing an in memory tree which will probably have
    * its line number information removed, we'll just use -1 for the line and
    * column numbers.
    * 
    * @param document DOCUMENT ME!
    * 
    * @throws SAXException DOCUMENT ME!
    */
   protected void documentLocator(Document document) throws SAXException
   {
      LocatorImpl locator = new LocatorImpl();

      String publicID = null;
      String systemID = null;
      DocumentType docType = document.getDocType();

      if(docType != null)
      {
         publicID = docType.getPublicID();
         systemID = docType.getSystemID();
      }

      if(publicID != null)
      {
         locator.setPublicId(publicID);
      }

      if(systemID != null)
      {
         locator.setSystemId(systemID);
      }

      locator.setLineNumber(-1);
      locator.setColumnNumber(-1);

      contentHandler.setDocumentLocator(locator);
   }

   protected void entityResolver(Document document) throws SAXException
   {
      if(entityResolver != null)
      {
         DocumentType docType = document.getDocType();

         if(docType != null)
         {
            String publicID = docType.getPublicID();
            String systemID = docType.getSystemID();

            if((publicID != null) || (systemID != null))
            {
               try
               {
                  entityResolver.resolveEntity(publicID, systemID);
               }
               catch(IOException e)
               {
                  throw new SAXException("Could not resolve publicID: "
                     + publicID + " systemID: " + systemID, e);
               }
            }
         }
      }
   }

   /**
    * We do not yet support DTD or XML Schemas so this method does nothing right
    * now.
    * 
    * @param document DOCUMENT ME!
    * 
    * @throws SAXException DOCUMENT ME!
    */
   protected void dtdHandler(Document document) throws SAXException
   {
   }

   protected void startDocument() throws SAXException
   {
      contentHandler.startDocument();
   }

   protected void endDocument() throws SAXException
   {
      contentHandler.endDocument();
   }

   protected void write(Element element, NamespaceStack namespaceStack)
      throws SAXException
   {
      int stackSize = namespaceStack.size();
      AttributesImpl namespaceAttributes =
         startPrefixMapping(element, namespaceStack);
      startElement(element, namespaceAttributes);
      writeContent(element, namespaceStack);
      endElement(element);
      endPrefixMapping(namespaceStack, stackSize);
   }

   /**
    * Fires a SAX startPrefixMapping event for all the namespaceStack which have
    * just come into scope
    * 
    * @param element DOCUMENT ME!
    * @param namespaceStack DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
    * 
    * @throws SAXException DOCUMENT ME!
    */
   protected AttributesImpl startPrefixMapping(Element element,
      NamespaceStack namespaceStack) throws SAXException
   {
      AttributesImpl namespaceAttributes = null;

      // start with the namespace of the element
      Namespace elementNamespace = element.getNamespace();

      if((elementNamespace != null)
         && !isIgnoreableNamespace(elementNamespace, namespaceStack))
      {
         namespaceStack.push(elementNamespace);
         contentHandler.startPrefixMapping(elementNamespace.getPrefix(),
            elementNamespace.getURI());
         namespaceAttributes =
            addNamespaceAttribute(namespaceAttributes, elementNamespace);
      }

      List declaredNamespaces = element.declaredNamespaces();

      for(int i = 0, size = declaredNamespaces.size(); i < size; i++)
      {
         Namespace namespace = (Namespace) declaredNamespaces.get(i);

         if(!isIgnoreableNamespace(namespace, namespaceStack))
         {
            namespaceStack.push(namespace);
            contentHandler.startPrefixMapping(namespace.getPrefix(), namespace
               .getURI());
            namespaceAttributes =
               addNamespaceAttribute(namespaceAttributes, namespace);
         }
      }

      return namespaceAttributes;
   }

   /**
    * Fires a SAX endPrefixMapping event for all the namespaceStack which have
    * gone out of scope
    * 
    * @param stack DOCUMENT ME!
    * @param stackSize DOCUMENT ME!
    * 
    * @throws SAXException DOCUMENT ME!
    */
   protected void endPrefixMapping(NamespaceStack stack, int stackSize)
      throws SAXException
   {
      while(stack.size() > stackSize)
      {
         Namespace namespace = stack.pop();

         if(namespace != null)
         {
            contentHandler.endPrefixMapping(namespace.getPrefix());
         }
      }
   }

   protected void startElement(Element element,
      AttributesImpl namespaceAttributes) throws SAXException
   {
      contentHandler.startElement(element.getNamespaceURI(), element.getName(),
         element.getQualifiedName(), createAttributes(element,
            namespaceAttributes));
   }

   protected void endElement(Element element) throws SAXException
   {
      contentHandler.endElement(element.getNamespaceURI(), element.getName(),
         element.getQualifiedName());
   }

   protected Attributes createAttributes(Element element,
      Attributes namespaceAttributes) throws SAXException
   {
      attributes.clear();

      if(namespaceAttributes != null)
      {
         attributes.setAttributes(namespaceAttributes);
      }

      for(Iterator iter = element.attributeIterator(); iter.hasNext();)
      {
         Attribute attribute = (Attribute) iter.next();
         attributes.addAttribute(attribute.getNamespaceURI(), attribute
            .getName(), attribute.getQualifiedName(), "CDATA", attribute
            .getValue());
      }

      return attributes;
   }

   /**
    * If isDelcareNamespaceAttributes() is enabled then this method will add the
    * given namespace declaration to the supplied attributes object, creating
    * one if it does not exist.
    * 
    * @param attrs DOCUMENT ME!
    * @param namespace DOCUMENT ME!
    * 
    * @return DOCUMENT ME!
    */
   protected AttributesImpl addNamespaceAttribute(AttributesImpl attrs,
      Namespace namespace)
   {
      if(declareNamespaceAttributes)
      {
         if(attrs == null)
         {
            attrs = new AttributesImpl();
         }

         String prefix = namespace.getPrefix();
         String qualifiedName = "xmlns";

         if((prefix != null) && (prefix.length() > 0))
         {
            qualifiedName = "xmlns:" + prefix;
         }

         String uri = "";
         String localName = prefix;
         String type = "CDATA";
         String value = namespace.getURI();

         attrs.addAttribute(uri, localName, qualifiedName, type, value);
      }

      return attrs;
   }

   /**
    * DOCUMENT ME!
    * 
    * @param namespace DOCUMENT ME!
    * @param namespaceStack DOCUMENT ME!
    * 
    * @return true if the given namespace is an ignorable namespace (such as
    *         Namespace.NO_NAMESPACE or Namespace.XML_NAMESPACE) or if the
    *         namespace has already been declared in the current scope
    */
   protected boolean isIgnoreableNamespace(Namespace namespace,
      NamespaceStack namespaceStack)
   {
      if(namespace.equals(Namespace.NO_NAMESPACE)
         || namespace.equals(Namespace.XML_NAMESPACE))
      {
         return true;
      }

      String uri = namespace.getURI();

      if((uri == null) || (uri.length() <= 0))
      {
         return true;
      }

      return namespaceStack.contains(namespace);
   }

   /**
    * Ensures non-null content handlers?
    */
   protected void checkForNullHandlers()
   {
   }
}

/*
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and
 * notices. Redistributions must also contain a copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived
 * from this Software without prior written permission of MetaStuff, Ltd. For
 * written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may
 * "DOM4J" appear in their names without prior written permission of MetaStuff,
 * Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
